﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using KuiHuaBaoDian.Services.Guide;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;

namespace KuiHuaBaoDian.GuideGraph.Editor {

    public sealed class GuideGraphView : GraphView {

        public new class UxmlFactory : UxmlFactory<GuideGraphView, UxmlTraits> { }

        public GuideGraphWindow Window;

        private GuideSection m_Section;
        private Vector2 m_ContextualMenuPopulateEventMousePosition;

        private Dictionary<Type, Type> m_NodeEditorTypeDictionary = new();
        private Dictionary<Type, Type> m_PropertyEditorTypeDictionary = new();
        private Dictionary<Guid, GuideGraphEntityNode> m_NodeDictionary = new();

        public IReadOnlyDictionary<Type, Type> PropertyEditorTypeDictionary => m_PropertyEditorTypeDictionary;

        private Blackboard m_Blackboard;

        public GuideGraphView() {
            Insert(0, new GridBackground());
            this.AddManipulator(new ContentZoomer());
            this.AddManipulator(new ContentDragger());
            this.AddManipulator(new SelectionDragger());
            this.AddManipulator(new RectangleSelector());
            var blackboard = GetBlackboard();
            //var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(NGF_GuideGraphWindow.USS_PATH);
            //styleSheets.Add(styleSheet);

            //新建搜索菜单
            var menuWindowProvider = ScriptableObject.CreateInstance<GuideGraphSearchMenuWindowProvider>();
            menuWindowProvider.SelectEntry = OnMenuWindowProviderSelectEntry;

            nodeCreationRequest += context => {
                SearchWindow.Open(new SearchWindowContext(context.screenMousePosition), menuWindowProvider);
            };

            var types = GuideGraphTypeUtility.GetEditorTypeEnumerable(new Type[] { typeof(GuideGraphEntityNode) });
            foreach (var type in types) {
                var customEditorAttribute = type.GetCustomAttribute<CustomElementAttribute>();
                if (customEditorAttribute != null) {
                    var targetType = customEditorAttribute.Type;
                    m_NodeEditorTypeDictionary[targetType] = type;
                }
            }
            foreach (var type in GetType().Assembly.GetTypes()) {
                if (typeof(IGuideProperty).IsAssignableFrom(type)) {
                    var customEditorAttribute = type.GetCustomAttribute<CustomElementAttribute>();
                    if (customEditorAttribute != null) {
                        var targetType = customEditorAttribute.Type;
                        m_PropertyEditorTypeDictionary[targetType] = type;
                    }
                }
            }
            types = GuideGraphTypeUtility.GetEditorTypeEnumerable(new Type[] { typeof(IGuideProperty) });
            foreach (var type in types) {
                var customEditorAttribute = type.GetCustomAttribute<CustomElementAttribute>();
                if (customEditorAttribute != null) {
                    var targetType = customEditorAttribute.Type;
                    m_PropertyEditorTypeDictionary[targetType] = type;
                }
            }
        }

        public override Blackboard GetBlackboard() {
            if (m_Blackboard == null) {
                m_Blackboard = new Blackboard(this);
                Add(m_Blackboard);
            }
            return m_Blackboard;
        }

        protected override bool canDeleteSelection => selection.Count > 0 && !selection.Any(select => select is GuideGraphEntityNode node && (node.Entity is IGuideRequired));

        private bool OnMenuWindowProviderSelectEntry(SearchTreeEntry searchTreeEntry, SearchWindowContext context) {
            if (searchTreeEntry.userData is Type type) {
                AddEntityNode(type, m_ContextualMenuPopulateEventMousePosition - placematContainer.worldBound.position);
                AssetDatabase.SaveAssets();
                return true;
            } else {
                return false;
            }
        }

        public void Populate(GuideSection section) {
            m_Section = section;
            graphViewChanged -= OnGraphViewChanged;
            m_NodeDictionary.Clear();
            DeleteElements(graphElements);
            graphViewChanged += OnGraphViewChanged;
            foreach (var entity in section.EntityList) {
                AddEntityNode(entity);
            }
            foreach (var entity in section.EntityList) {
                AddEdge(entity);
            }
        }

        private GraphViewChange OnGraphViewChanged(GraphViewChange graphViewChange) {
            graphViewChange.elementsToRemove?.ForEach(element => {
                if (element is GuideGraphEntityNode node) {
                    m_Section.RemoveEntity(node.Entity);
                } else if (element is Edge edge) {
                    if (edge.output.node is GuideGraphEntityNode outputNode) {
                        outputNode.RemoveEdge(edge);
                        AssetDatabase.SaveAssets();
                    }
                }
            });
            graphViewChange.edgesToCreate?.ForEach(edge => {
                if (edge.output.node is GuideGraphEntityNode outputNode) {
                    outputNode.AddEdge(edge);
                    AssetDatabase.SaveAssets();
                }
            });
            return graphViewChange;
        }

        private void AddEntityNode(Type type, Vector2 position) {
            if (m_Section == null) {
                return;
            }
            var entity = m_Section.AddEntity(type);
            entity.Position = position;
            AddEntityNode(entity);
        }

        //private void AddEntityNode(NGF_GuideEntity entity, Vector2 position) {
        //    var node = AddEntityNode(entity);
        //    var rect = node.GetPosition();
        //    rect.x = position.x + 24;
        //    rect.y = position.y + 24;
        //    node.SetPosition(rect);
        //}

        private GuideGraphEntityNode AddEntityNode(GuideEntity entity) {
            if (entity == null || entity.GUID == Guid.Empty) {
                return null;
            }
            var node = CreateNode(entity);
            AddElement(node);
            m_NodeDictionary[entity.GUID] = node;
            return node;
        }

        private void AddEdge(GuideEntity entity) {
            if (entity == null || entity.GUID == Guid.Empty) {
                return;
            }
            var node = m_NodeDictionary[entity.GUID];
            var edge = default(Edge);
            if (m_NodeDictionary.TryGetValue(entity.OutputTo, out var outputToNode)) {
                edge = node.PortOutput.ConnectTo(outputToNode.PortInput);
                if (edge != null) {
                    AddElement(edge);
                }
            }
            if (m_NodeDictionary.TryGetValue(entity.NextTo, out var nextToNode)) {
                edge = node.PortNext.ConnectTo(nextToNode.PortPrev);
                if (edge != null) {
                    AddElement(edge);
                }
            }
        }

        //private void RemoveEntityNode(NGF_GuideEntityNode node) {
        //    RemoveElement(node);
        //    m_Section.RemoveEntity(node.Entity);
        //}

        public override void BuildContextualMenu(ContextualMenuPopulateEvent evt) {
            if (evt.target is GraphView && nodeCreationRequest != null) {
                evt.menu.AppendAction("Create Node", OnContextMenuNodeCreate, DropdownMenuAction.AlwaysEnabled);
                evt.menu.AppendSeparator();
            }
            if (evt.target is GraphView || evt.target is Node || evt.target is Group || evt.target is Edge) {
                evt.menu.AppendSeparator();
                evt.menu.AppendAction("Delete", delegate {
                    DeleteSelectionCallback(AskUser.DontAskUser);
                }, (DropdownMenuAction a) => canDeleteSelection ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled);
            }
            m_ContextualMenuPopulateEventMousePosition = evt.mousePosition;
        }

        private void OnContextMenuNodeCreate(DropdownMenuAction a) {
            RequestNodeCreation(null, -1, a.eventInfo.mousePosition);
        }

        private void RequestNodeCreation(VisualElement target, int index, Vector2 position) {
            if (nodeCreationRequest != null) {
                nodeCreationRequest(new NodeCreationContext {
                    screenMousePosition = Window.position.position + position,
                    target = target,
                    index = index
                });
            }
        }

        public override List<Port> GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter) {
            var list = new List<Port>();
            if (startPort.ClassListContains(GuideGraphEntityNode.PORT_CLASS_NEXT)) {
                list = list.Concat(this.Query<Port>(className: GuideGraphEntityNode.PORT_CLASS_PREV).Where(port => port.node != startPort.node).Where(port => CanConnect<NodeAcceptPrevAttribute>(startPort, port)).Where(port => CanConnect(port.node, false, startPort.node, true)).ToList()).ToList();
            } else if (startPort.ClassListContains(GuideGraphEntityNode.PORT_CLASS_PREV)) {
                list = list.Concat(this.Query<Port>(className: GuideGraphEntityNode.PORT_CLASS_NEXT).Where(port => port.node != startPort.node).Where(port => CanConnect<NodeAcceptPrevAttribute>(port, startPort)).Where(port => CanConnect(port.node, false, startPort.node, false)).ToList()).ToList();
            }
            if (startPort.ClassListContains(GuideGraphEntityNode.PORT_CLASS_OUTPUT)) {
                list = list.Concat(this.Query<Port>(className: GuideGraphEntityNode.PORT_CLASS_INPUT).Where(port => port.node != startPort.node).Where(port => CanConnect<NodeAcceptInputAttribute>(startPort, port)).Where(port => CanConnect(port.node, true, startPort.node, true)).ToList()).ToList();
            } else if (startPort.ClassListContains(GuideGraphEntityNode.PORT_CLASS_INPUT)) {
                list = list.Concat(this.Query<Port>(className: GuideGraphEntityNode.PORT_CLASS_OUTPUT).Where(port => port.node != startPort.node).Where(port => CanConnect<NodeAcceptInputAttribute>(port, startPort)).Where(port => CanConnect(port.node, true, startPort.node, false)).ToList()).ToList();
            }
            return list;
        }

        private GuideGraphEntityNode CreateNode(GuideEntity entity) {
            var entityType = entity.GetType();
            if (m_NodeEditorTypeDictionary.TryGetValue(entityType, out var nodeType)) {
                var constructor = nodeType.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(GuideEntity), typeof(GuideGraphView) }, null);
                return (GuideGraphEntityNode)constructor.Invoke(new object[] { entity, this });
            } else {
                return new GuideGraphEntityNode(entity, this);
            }
        }

        private bool CanConnect<T>(Port start, Port end) where T : NodeAcceptAttribute {
            var startType = (start.node.userData as GuideEntity).GetType();
            var endType = (end.node.userData as GuideEntity).GetType();
            var acceptAttributes = endType.GetCustomAttributes<T>();
            var result = true;
            if (acceptAttributes != null) {
                result = false;
                foreach (var acceptAttribute in acceptAttributes) {
                    if (acceptAttribute.Type.IsAssignableFrom(startType)) {
                        result = true;
                        break;
                    }
                }
            }
            return result;
        }

        private bool CanConnect(Node node, bool isHorizontal, Node self, bool selfIsStart) {
            var result = true;
            if (selfIsStart) {
                var outputPort = isHorizontal ? (node.outputContainer.childCount > 0 ? node.outputContainer[0] as Port : null) : (node as GuideGraphEntityNode).PortNext;
                if (outputPort != null) {
                    var edge = outputPort.connections.FirstOrDefault();
                    if (edge != null) {
                        var InputNode = edge.input.node;
                        if (self == InputNode) {
                            return false;
                        }
                        result = CanConnect(InputNode, isHorizontal, self, selfIsStart);
                    }
                }
            } else {
                var inputPort = isHorizontal ? (node.inputContainer.childCount > 0 ? node.inputContainer[0] as Port : null) : (node as GuideGraphEntityNode).PortPrev;
                if (inputPort != null) {
                    var edge = inputPort.connections.FirstOrDefault();
                    if (edge != null) {
                        var outputNode = edge.output.node;
                        if (self == outputNode) {
                            return false;
                        }
                        result = CanConnect(outputNode, isHorizontal, self, selfIsStart);
                    }
                }
            }
            return result;
        }
    }
}
